Chapter 8: Lists
From book Python Programming (Problem solving, Packages and Libraries) published by McGraw Hill Education (India) Private limited. By:

  • Anurag Gupta
  • G. P. Biswas

Note the following:-

  1. This html document is meant as an accompaniment to Chapter 8 Lists .
  2. The document contains scripts executed on IDLE as well as on Jupyter notebook.
  3. The scripts executed on Jupyter can be directly copied and run into a Jupyter notebook or some other IDE (Like Pycharm or Eclipse with PyDev or Visual studio).
  4. However the scripts on IDLE also contain the >>> symbol and therefore cannot be directly executed. If you want to execute them on IDLE or Jupyter, you need to manually remove the >>> symbol.
  5. Wherever needed some background material from the book is also included to help you better understand the scripts
  6. The topic numbers given on each paragraph match the topic numbers of the book, so you can easily identify the topics and corresponding scripts.
  7. At some places, to improve readability, page numbers of the book are indicated in green font like:- See Page 181 of the book
  8. In some of the scripts, the file paths give are that of the author's computer. You need to replace them with file paths of your own computer.
  9. This document was first created as a Jupyter Notebook as combination of Markdown and code cells (extension .ipynb) and then downloaded as html. If someone wants to "modify" or "extend' this document, you may ask for the original .ipynb file by sending me an e-mail at:- 999.anuraggupta@gmail.com

8.2. Some basic concepts of lists
8.2.1. Concept of containers(or collections), sequence and mapping
(iii) Traversal or iteration:
Going over the items of a container (whether sequence or map) is called traversal or iteration. Programming languages, such as Python provide methods or functions to the programmer to go over the various objects in a container. These methods are called iterators . All iterables in Python allow access to elements using the for ... in statement. General expression for iteration over a container in Python is as follows:

for item in myC: # Here myC represents a container
    # Do something

The following script takes an example of a script, which uses a string as an iterable:

In [1]:
myC = 'cat'
for item in myC: # Here myC is the string ‘cat'
    print(item)
c
a
t

8.2.4. Some common operations on sequences
(i) seq[i]
This is the ith item of the sequence. The count for sequence in Python always starts from 0.
To access the ith item of a sequence in Python, you always use square brackets, that is, [] and never round brackets, that is, (). If a sequence has n items, then the index can be from 0 to n-1 or from -1 to –n. The concept of negative has been explained earlier for strings. It also applies to other sequences, such as lists and tuples. Accessing the individual items of various types of sequences is shown as follows (We have shown accessing elements of literal sequences as well as of variables holding sequences):

# ---ON IDLE---  
>>>'abcd'[2] # 'abcd' is a literal string
'c'
>>> ['a', 'b', 'c', 'd'][-1] # This is a literal list
'd'
>>> ('a', 'b', 'c', 'd')[-4]# This is a tuple
'a'
>>> seq = 'abcd'# seq is a variable holding a string type
>>> seq[1]
'b'
>>> myL = ['a', 'b', 'c', 'd'] # myL holds a list
>>> myL[2]
'c'
>>> myT = ('a', 'b', 'c', 'd') # myT holds a tuple
>>> myT[3]
'd'

(ii) x in seq
(Where seq is a Python sequence of type string, list or tuple)
If the sequence seq has an item x, then this will evaluate to True, else False.

# ---ON IDLE---
>>>'a' in 'abc'#'abc' is a string
True
>>>'a' in ['a', 'b', 'c'] # ['a', 'b', 'c'] is a list so also a sequence
True
>>>'a' in ('a', 'b', 'c') # ('a', 'b', 'c') is a tuple so also a sequence
True
>>>'ab' in 'abc'# Note for strings the in also holds for substrings
True
>>> ['a', 'b'] in ['a', 'b', 'c'] # But not for lists
False

(iv) seq1 + seq2
(Here, seq1 and seq2 are sequences of the same type)
The addition or concatenation operator, that is, + can be used to concatenate or join two sequences of the same type. So, a string can be added or concatenated to another string using +. Similarly, lists and tuples can also be concatenated to each other. Note that since tuples are immutable, concatenation of tuples does not modify the original tuple but rather creates a new tuple. You cannot use the + operator to join two sequences of different types. So you cannot use + to join say a string to a list.

# ---ON IDLE---
>>>'abc' + 'cde'  # concatenate ie joins two strings
'abccde'
>>> ['a', 'b', 'c'] + ['d', 'e', 'f'] # concatenate two lists
['a', 'b', 'c', 'd', 'e', 'f']
>>> ('a', 'b', 'c') + ('d', 'e', 'f') # concatenate two tuples
('a', 'b', 'c', 'd', 'e', 'f')

(v) seq * n or n * seq
(Here, seq is a sequence and n is a positive integer)

seq * n will create n copies of the sequence. But if n is 0, it will create an empty sequence.

# ---ON IDLE---  
>>>'abc' * 3            # Creates 3 copies of the string 'abc'
'abcabcabc'
>>> ['a', 'b', 'c'] * 3 # Creates 3 copies of the list
['a', 'b', 'c', 'a', 'b', 'c', 'a', 'b', 'c']
>>> ('a', 'b', 'c') * 3 # Creates 3 copies of the tuple
('a', 'b', 'c', 'a', 'b', 'c', 'a', 'b', 'c')
>>>'abc' * 0            # Creates an empty string
''
>>> ['a', 'b', 'c'] * 0 # Creates an empty list
[]
>>> ('a', 'b', 'c') * 0 # Creates an empty tuple
()

(vii) seq[i : j]
(Here i and j are both indices of the sequence and must be in range 0 to n-1 or -1 to –n; where n is the number of items in the sequence)

  • This is used to create a slice of the sequence.
  • Note that the item at index i is included in the slice but the item at index j is NOT included in the sequence. So, the sequence will consist of items from index i to j-1.
  • Further note, that here the index j must refer to an item ‘further or ahead’ in sequence than i else there will be an empty sequence.

What does ‘further’ mean? Further means that if both **`i`** and **`j`** are positive integers then **`j`** must be > than **`i`**. If **`j ≤ i`**, then it will create an empty sequence. For negative integers also **`j`** must be **`> i`**. If **`j ≤ i`**, then also it will generate an empty sequence. The third possibility is that one of the indices is a positive integer and the other is a negative integer. Here also, the **`j`** index must refer to an item ‘further’ than **`i`** or else it will generate an empty sequence.

Take a sequence, say, [‘a’, ‘b’, ‘c’, ‘d’]. Now you can create slices of this list using positive and negative indices as follows:

# ---ON IDLE---  
>>> myL = ['a', 'b', 'c', 'd']
>>> myL[1:3]
['b', 'c']
>>> myL[-3:-1] # For list with 4 items, Index -3 same as 1 and index -1 same as 3
['b', 'c']

Take an example, where slice [i: j] the index of j is less than i thereby giving an empty list:

# ---ON IDLE---
>>> myL = ['a', 'b', 'c', 'd']
>>> myL[3:0] # 0 is not 'further' than 3 so generates an empty sequence
[]
>>> myL[-2:-4] " #-4 is not 'further' than -2 so empty sequence
[]

(viii) seq[i: j: k] (Here i, j, k are integers)

  • The function seq[i: j: k] can be to pick out certain elements in a sequence using a ‘step’ k.
  • The slice of the given sequence seq from i to j with step k contains those items of the sequence with index $x = i + n*k$ such that $0 <= n < (j-i)/k$. So the items included in the slice are those with indices:- $i, i+k, i+2*k, i+3*k$ and so on. Note that the slice stops when j is reached (but j is not to be included).

For instance, if you have a string of 10 numbers say myL = [11, 12, 13, 14, 15, 16, 17, 18, 19, 20], that is, index vary from 0 to 9, then you can do the following:

# ---ON IDLE---
>>> myL = [11, 12, 13, 14, 15, 16, 17, 18, 19, 20]
>>> myL[1:9:3]# Will include items at index 1, 1+3*1 ie 4, 1+ 3*2 ie 7
[12, 15, 18]
>>> myL[0:9:2]# Will include all odd items in the sequence
[11, 13, 15, 17, 19]
>>> myL[1:9:2]#Gives even items except last ie items 1,3,5,7 but not at index 9
[12, 14, 16, 18]
>>> myL[1:10:2]# To include last item, use index j greater than 9.
[12, 14, 16, 18, 20]
>>> myL[1:100:2]# You can use a large value like 100 also for j
[12, 14, 16, 18, 20]
>>> myL[1:100:3]
[12, 15, 18]

See Page 181 of the book

(i) If in place of ‘i’ or ‘j’ you use an index, which is greater than n-1 (Where n is the number of items), then it will be taken to be len(seq).
Remember, len(seq) is a function, which gives the length of the sequence.
If a sequence, say seq has 10 items, then its index can vary from 0 to 9 but len(seq) will be 10. Hence, you can use an integer greater than len(seq), but the Python interpreter will treat it as len(seq).
This will be clear from the following:

# ---ON IDLE---
>>> myL = [11, 12, 13, 14, 15, 16, 17, 18, 19, 20]
>>> len(myL)# len(myL) will resolve to 10 since 10 items in list
10
>>> myL[1:len(myL):2]# Will give items at index 1,3,5,7 and 9.
[12, 14, 16, 18, 20]

See Page 181 of the book
(ii) If you leave out any of the i or j, it will be treated as the beginning and the ends of the sequence respectively, if k is positive.
However, if k is negative, then i will be treated as end while j will be treated as the beginning of the sequence.
This is clear from following:

# ---ON IDLE---  
>>> myL = [11, 12, 13, 14, 15, 16, 17, 18, 19, 20]
>>> myL[ : :2]# Here i will become 0 and j will become 9.
[11, 13, 15, 17, 19]
>>> myL[ : : -1]# Here i will become 9 and j will become 0.List reversed
[20, 19, 18, 17, 16, 15, 14, 13, 12, 11]

Note: k cannot be zero. If k is None, it is treated like 1. Hence:-

# ---ON IDLE---  
>>> myL = [11, 12, 13, 14, 15, 16, 17, 18, 19, 20]
>>> myL[ : : None]# None is treated as 1
[11, 12, 13, 14, 15, 16, 17, 18, 19, 20]
>>> myL[ : : 0]# Value of 0 for k is not allowed. Error
Traceback (most recent call last):
File "<pyshell#37>", line 1, in<module>
    myL[ : : 0]
ValueError: slice step cannot be zero

If either or both of i and j are negative, the index is relative to the end of the sequence, then they are substituted by i + len(seq) or j + len(seq), as shown in the following example:

# ---ON IDLE---  
>>> myL = [11, 12, 13, 14, 15, 16, 17, 18, 19, 20]
>>> myL[-9:-1:2]# Index -9 same as -9 + 10 ie 1, index -1 same as -1 + 10 ie 9
[12, 14, 16, 18]

8.3. Creating, treversing and slicing lists
8.3.1. Creating list
Table 1.3, in the book shows the various ways of constructing lists in Python.
This will become clear from following examples:

# ---ON IDLE---  
>>> eL = [] # Will create an empty list held by variable eL
>>> eL
[]
>>> oL = [1] # Will create a list with a single integer 1
>>> oL
[1]
>>> myL = ['a', 'cat', 1] # Will create a list with 3 items
>>> myL
['a', 'cat', 1]

Let us now look at some examples of creating a list from iterators and functions/ methods.

# ---ON IDLE---  
>>> alphab = 'abcdefghi'
>>> list(alphab) # The list() function splits string into characters
['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i']
>>> words = 'I am OK'# This string of words has 3 words and 2 empty spaces
>>> list(words) # Empty spaces also become characters in list
['I', ' ', 'a', 'm', ' ', 'O', 'K']
>>> words.split() # But if you want words then use split() method of string object
['I', 'am', 'OK']

Let us look at an example that uses the range() function to create a list. The range() function is covered later in detail. Only one simple form of range() function is used here, that is, range(n) where n is a positive integer. range(n) will generate numbers from 0 to n-1.

# ---ON IDLE---  
>>> myL = [ x for x in range(5)] #Create list of integers from 0 to 4
>>> myL
[0, 1, 2, 3, 4]
>>> [x*2for x in range(6)] #Create list of square of integers from 0 to 5
[0, 2, 4, 6, 8, 10]

8.3.2. List comprehension (Comprehension means construction)
Comprehensions are constructs that allow sequences to be built from other sequences. This will become clear shortly. To create a list from another sequence, you need the following four things:

  1. An input sequence
  2. A variable representing a member of the input sequence
  3. An output expression
  4. An optional condition (Also called a predicate expression)

This will become clear from the following example: Suppose you have a list of natural numbers from 1 to 10 and you want to create from this list another list of squares of all the odd numbers in the list. Then the original list is L1, that is, [1,2,3,4,5,6,7,8,9,10] and the newly-created list will be L2, that is, [1,9,25,49,81]. This can be done as follows:

# ---ON IDLE---  
>>> L1 = [1,2,3,4,5,6,7,8,9,10]
>>> L2 = [x**2 for x in L1 if x//2 != 0]
>>> L2
[4, 9, 16, 25, 36, 49, 64, 81, 100]

Please note that the condition or predicate is optional. Following is the same code without a predicate. It will give squares of all the numbers in the list:

# ---ON IDLE---  
>>> L1 = [1,2,3,4,5,6,7,8,9,10]
>>> L2 = [x**2for x in L1 ] # No condition ie no predicate
>>> L2
[1, 4, 9, 16, 25, 36, 49, 64, 81, 100]

As another example, suppose you have a list of names and you want to use list comprehension to only pick out those names, which begin with a certain letter. This can be done as shown:

# ---ON IDLE---  
>>> names = ['Anil', 'Asha', 'Bob', 'Deepika', 'Franz', 'Rose', 'Farah']
>>> firstL = ['A', 'F']
>>> selNames = [ x for x in names if x[0] in firstL]
>>> selNames
['Anil', 'Asha', 'Franz', 'Farah']

8.3.3. Traversing the list (ie iterating over items of a list)
Traversing a list means accessing elements of a list one by one. There are various ways of traversing a list. Some of the common methods are as follows:

  1. Using a while loop along with len() function to get the length of the list**
  2. Using for/ in combination along with range() function
  3. Using for/in combination treating the list as a collection (that is, without using index of the items)

These three methods of traversing a list are explained as follows:
(i) Using a while loop along with len() function to get the length of the list.

In [2]:
pets = ["Dog", "Cat", "Fish", "Parrot"]
i = 0
while i < len(pets):
    print(pets[i])
    i += 1
Dog
Cat
Fish
Parrot

See Page 187 of the book
(ii) Using for/in combination along with range() function.

In [3]:
pets = ["Dog", "Cat", "Fish", "Parrot"]
for i in range(len(pets)):
    print(pets[i])
    i += 1
Dog
Cat
Fish
Parrot

(iii) Using for/in combination treating the list as a collection (that is, without using index of the items)
Here, the list can be treated as a collection of items in the list. The index of the items of the list are not used. The following code will make the concept clear:

In [4]:
pets = ["Dog", "Cat", "Fish", "Parrot"]
for pet in pets:
    print(pet)
Dog
Cat
Fish
Parrot

The following points about traversing a list are also noteworthy:

  • If the list is empty, then a loop is not executed.

This will be clear from the following code:

In [5]:
pets = []
for pet in pets:
    print(pet)
    print('test')
# There will be no output

See Page 188 of the book
(i) Create a list of squares of numbers from 1 to 10.
Without list comprehension, the code is as follows:

In [6]:
squares = []
for x in range(10):
    squares.append((x+1)**2) # range(10) is from 0 to 9 so use x+1
print(squares)
[1, 4, 9, 16, 25, 36, 49, 64, 81, 100]

With list comprehension, the code is as follows:

In [7]:
squares = [ (x+1)**2for x in range(10)]
print(squares)
[1, 4, 9, 16, 25, 36, 49, 64, 81, 100]

(ii) Some more examples of list comprehension are as follows:

# ---ON IDLE---  
>>> myL = [ -10, -6, -2, 0, 4, 8, 12]
>>> [(x+2) for x in myL]
[-8, -4, 0, 2, 6, 10, 14]
>>> [ x**2for x in myL]
[100, 36, 4, 0, 16, 64, 144]
>>> [abs(x) for x in myL]
[10, 6, 2, 0, 4, 8, 12]
>>> [[x, x**2] for x in myL]
[[-10, 100], [-6, 36], [-2, 4], [0, 0], [4, 16], [8, 64], [12, 144]]
>>>

(iii) Use list comprehension to remove vowels from a sentence.

In [8]:
s1 = 'A quick brown fox jumped over a lazy dog'
vowels = ['a', 'e', 'i', 'o', 'u']
s2 = ''.join([x for x in s1 if x not in vowels]);
print(s2)
A qck brwn fx jmpd vr  lzy dg

8.3.4. Ways of adding to a list
There are four ways of adding to a list, as follows:

  1. Using the + operator
  2. List function: insert()
  3. List function: append()
  4. List function: extend()

(i) Using the + operator: The + operator concatenates lists just like it concatenates strings. This is because when the Python interpreter sees a + operator, it sees the two operands on both sides (LHS and RHS) of the operator. If these two operands are lists, then the Python Interpreter is smart enough to understand that these lists have to be joined to each other to form a new list.
Therefore, in Python, a plus sign, that is, + has different meanings depending upon the operands on which it is operating.

# ---ON IDLE---  
>>> L1 = ['a', 'b', 'c']
>>> L2 = ['c', 'd', 'e']
>>> L1 + L2
['a', 'b', 'c', 'c', 'd', 'e']

(ii) List function: insert()
The noteworthy points about the insert() method are as follows:

  • The insert(m, item) method takes two arguments. The first argument is the index position, where the new item is added and the second parameter is the item to be added.
  • When you insert an item using insert(), the item originally present at that index moves one index ahead.

Following examples clarify the concept:

# ---ON IDLE---  
>>> myL = ['A', 'quick', 'fox']
>>> myL.insert(2, 'brown') # Will insert 'brown' at index 2
>>> myL
['A', 'quick', 'brown', 'fox']

(iii) List function: append()
The append() method adds an item to the end of the list. Note that the append method can add an item only at the end of the list.

# ---ON IDLE---  
>>> myL = [1,2,3] 
>>> myL.append(4) # append() is a function so use append(4) not append[4]
>>> myL
[1, 2, 3, 4]
>>> myL.append([5,6]) # Can append any data type to a list including another list
>>> myL
[1, 2, 3, 4, [5, 6]]

(iv) List function:- extend()
Noteworthy points regarding the extend() method are as follows:

  • The extend() method takes a single argument. The argument to the extend() method must also be a list.
  • The extend() method adds each item in the list given as argument at the end of the original list on which the extend() method is applied.
  • So the extend() function is something like the + operator. If you have two lists say L1 and L2, then L1.extend(L2) is the same as L1 + L2.
# ---ON IDLE---  
>>> L1 = [1,2,3,4]
>>> L2 = [5,6,7,8]
>>> L1.extend(L2) # Argument to .extend() must be a list-> Here it is L2
>>> L1
[1, 2, 3, 4, 5, 6, 7, 8]
>>> L1.extend([9,10]) # Can give list like [9, 10] as argument to .extend() also
>>> L1
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

8.3.6. myList[start: end] or myList[m:n]
Slicing a list does not change the original list. However, if the slice is assigned to another variable, a new list will be created.

# ---ON IDLE---  
>>> myL = ['a', 'b', 'c', 'd', 'e', 'f']
>>> myL2 = myL[1:4] # Now myL2 'points to' a slice of original list
>>> myL # The slicing does not change the original list
['a', 'b', 'c', 'd', 'e', 'f']
>>> myL2 # myL2 is a different list from myL
['b', 'c', 'd']
>>> id(myL)
36623384
>>> id(myL2)
36623224

8.3.7. myList[m:n:s] or mylist[start:end:step]
Here, there is a value for the step attribute also, so the slice may skip over some of the elements, depending upon the value of step attribute. Note that if step is 1 then no items are skipped and if step is -1 then the slice moves in the reverse direction. The following example code clarifies the concept:

In [9]:
from _operator import index
myL = [1, 2, 3, 4, 5, 6, 7, 8]
myL2 = myL[::2]  # Picks items at even index
print(myL2)
myL3 = myL[1::2]  # Picks items at odd index
print(myL3)
myL4 = myL[-1::-1] # Reverse the list
print(myL4)
[1, 3, 5, 7]
[2, 4, 6, 8]
[8, 7, 6, 5, 4, 3, 2, 1]

8.3.8. Index and slice assignment
You can modify a slice of the original list also. Therefore, a slice operation can be used to assign new values to a slice of a list. This can be shown as follows:

# ---ON IDLE---  
>>> myL = [1, 2, 3, 4, 5]
>>> id(myL)
36609320
>>> myL[0:3] = ['one', 'two', 'three'] # Change 3 items in a slice of list
>>> myL
['one', 'two', 'three', 4, 5]
>>> id(myL) # id of myL doesnt change-> same list
36609320

8.3.9. Difference between assigning a list to another list and copying a list
Assigning a list to another list variable does not create another object. It simply creates another variable name for the same object. However, copying a list into another list creates another object. Note that using [:] copies a list. This is shown in the following example:

# ---ON IDLE---  
>>> myL1 = ['a', 'b', 'c']
>>> id(myL1)
36958656
>>> myL2 = myL1
>>> id(myL2) # id(myL1) same as id(myL2) -> myL1 and myL2 refer to same object
36958656
>>> myL3 = myL1[:]
>>> myL3 # myL2 is a copy of myL1
['a', 'b', 'c']
>>> id(myL3) # id(myL3) not same as id(myL1) -> myL3 different object from myL1
33622336
>>> myL1[2] = 'cat'  # Change an item of myL1
>>> myL2 # Changing myL1 automatically changes myL2
['a', 'b', 'cat']
>>> myL3 #But changing myL1 does not change myL3
['a', 'b', 'c']

8.3.10. Concept of aliasing
An alias is an alternate name for the same object.
In Python, variables refer to objects, if you assign a variable to another, both variables will refer to the same object and an alias is created.
If the object being referred to is immutable, (that is, it cannot be modified in place), then creating an alias will not make a difference.
However, if the object being referred to is mutable, then aliasing can lead to unexpected bugs.
For instance, take a string and create its alias. Reassigning a new value to the alias doesn’t modify the original string.

# ---ON IDLE---  
>>> s1 = 'happy'
>>> s2 = s1
>>> s2 = 'sad'# s2 is assigned a new value. But s1 doesn’t change
>>> s1
'happy'

Now try the same thing on a list and change its data in-place.

# ---ON IDLE---  
>>> myL1 = [1,2,3]
>>> myL2 = myL1
>>> myL2.append(4) # Change myL2 in place
>>> myL1        # Also changes myL1
[1, 2, 3, 4]

This can also happen if a list is passed to a function call. If a list is changed inside a function call, then the change will reflect in the original list. Hence, unless you specifically want this to happen, you should be very careful in passing lists as parameters to functions. This is shown in the following script:

In [10]:
def f1(myL):            #function definition
    myL[0] = 'New'      # Will change item at index 0 of list to 'New'
    myL[1] = 'Bottle'   # Changes item at index 1 to 'Bottle'

myL2 = ['Old','wine']
print('List before ', myL2)
f1(myL2)         #  Function f1() is called here and changes list myL2
print('List after ',myL2)
List before  ['Old', 'wine']
List after  ['New', 'Bottle']

See Page 194 of the book
8.4.2. sort()
Python has both functions and methods for sorting a list:

  • There is a function called sorted() which takes a list object and sorts.
  • There is also a sort() method which acts on a list object (Using a dot operator).

Note that sorted() is a function, which takes a list as an argument. But on the other hand, sort() is a method of a list object so it is used with the dot operator and doesn’t take an argument. An example of use of sorted() function and sort() method on a list is as follows:

# ---ON IDLE---  
>>> myL = [3, 2, 6, 9, 0]
>>> mySortedL = sorted(myL) # Using function sorted() giving it myL as argument
>>> mySortedL            # sorted() creates a new list
[0, 2, 3, 6, 9]
>>> myL            # sorted() does not change the original list
[3, 2, 6, 9, 0]
>>> myL.sort()            # However sort() method changes myL “in-place”
>>> myL
[0, 2, 3, 6, 9]

Sorting in descending order
Both sorted function and sort() method take a reverse parameter whose default value is false. But if the reverse parameter is true, then Python will sort in descending order.

# ---ON IDLE---  
>>> L1 = [0,2,3,6,9]
>>> rL1 = sorted(L1, reverse = True)
>>> rL1
[9, 6, 3, 2, 0]
>>> L1 # Again L1 is unaffected
[0, 2, 3, 6, 9]
>>> L2 = ['a', 'A', 'b', 'B', 'z', 'Z']
>>> L2.sort(reverse = True)
>>> L2 # Small letters -> higher ASCII. Descending order all small before all capital
['z', 'b', 'a', 'Z', 'B', 'A']

8.4.3. Searching, adding, removing, reversing, and so on
You can search for an item in a list as follows:

# ---ON IDLE---  
>>> L = [1, 'a', 2, 'b']
>>>'a' in L    #This returns a True or False. Here it is True
True
>>>'c' in L # Since 'c' is not in L -> False
False
>>> flag = 'a' in L # The return of 'a' in L can be assigned to a variable
>>> flag
True

8.4.4. index() method
We can use the index() method to find the index of an item in the list if the item is present in the list. But if the item is not present in the list, there will be an error as follows:

# ---ON IDLE---  
>>> myL =[1,'a',2,'b',1] # There are two instances of 1 in this list
>>> myL.index(1) # Gives index of first instance of 1. So 0 not 4
0
>>> myL.index('c') # ERROR. Since 'c' not in list myL
Traceback (most recent call last):
  File "<pyshell#101>", line 1, in<module>
    myL.index('c')
ValueError: 'c' is not in list

8.4.5. List method: pop()
The following about the pop() function are relevant:

  1. The pop() method of a list can be called with or without arguments.
  2. If no argument is given to the pop() method, then this method removes the last item in the list and returns the item it removed.
  3. You can give an argument to the pop method. This argument must be the index of the item you want to pop.
  4. The popped item is returned by the method. Further, the item at an index just greater than the popped item takes the place of the popped item in the list.
  5. Similarly, all other items with index greater than that of the popped item shift by 1 index to the left, that is, their index gets decremented by 1.
  6. This way the gap created in the list by the popped item gets filled up. If you call the pop() function on an empty list, an exception is raised.
    Following examples clarify the concepts:
# ---ON IDLE---  
>>> L = [0, 'one', 2, 'three', 4, 'five', 6]
>>> L.pop() #Pops last item in the list
6
>>> L # Original list loses last item ie 6
[0, 'one', 2, 'three', 4, 'five']
>>> p = L.pop(1) # Pops item at index 1 ie second item and returns it
>>> p # Variable p now holds or points to popped item
'one'
>>> L #Again list loses item at index 1
[0, 2, 'three', 4, 'five']
>>> L.pop(5) # ERROR since list does not have an item at index 5
Traceback (most recent call last):
  File "<pyshell#108>", line 1, in<module>
    L.pop(5)
IndexError: pop index out of range
>>> empL =[]
>>> empL.pop() #ERROR. Cannot pop() an empty list
Traceback (most recent call last):
  File "<pyshell#110>", line 1, in<module>
    empL.pop()
IndexError: pop from empty list

8.4.6. List in Boolean context
An empty list in the Boolean context is False. A list with at least one item (even an empty string) will evaluate to True.

# ---ON IDLE---  
>>> bool([])        # An empty list evaluates to False
False
>>> bool([''])     #A list with empty string ‘’ is not empty. It has 1 item ie ‘’
True

8.5. Nested lists and using them as Matrix/ Matrices
In Python, lists can be nested to get multi-dimension lists. A two-dimension list can be used as a matrix. For instance, a basic 3 x3 matrix in Python can be represented as: M = [[10,20,30],[40,50,60],[70,80,90]] Here [10,20,30] is the first row, [40,50,60] is the second row and [70,80,90] is the third row.
Remember, the rows and columns in matrices in maths are represented using notation r x c where r represents the rows and c represents the columns.
Further note that the row and column numbers start from 1, while the lists used to represent matrices start from index 0.
On Python IDLE, if you don’t close a bracket and go to a new line, then the interpreter is smart enough to understand that more input is expected and so it waits for more input. This can be used to make the matrix more readable as shown:

# ---ON IDLE---  
>>>M = [[10, 20, 30],  #Outer bracket not closed. More input expected
    [40, 50, 60],
    [70, 80, 90]] # Now outer bracket closed so end of input
>>>

To access individual items you need to use two indices. For instance, M[0][0] is for 10, similarly M[2][1] is for the second item in the third row, that is, 80. If you use only one index, it will give the entire row, that is, it will give a list.

# ---ON IDLE---  
>>> M = [[0,1,2],[3,4,5],[6,7,8]]
>>> M[0][2] #Gives 3rd item (Index 2) in 1st (Index 0) inner list
2
>>> M[2]    # Gives inner list at index 2 ie 3rd row
[6, 7, 8]

Suppose you are asked to create a matrix of some dimension, say m x n and fill up its rows and columns with some data. You have to remember that in Python you cannot create a variable without assigning it some value. Hence, you have to initialize it with some data. Suppose you want to initialize a 4 x 5 matrix with all zeros. You can do it as follows:

# ---ON IDLE---  
>>> M = [[0 for x in range(5)] for y in range(4)]
>>> M
[[0, 0, 0, 0, 0], [0, 0, 0, 0, 0], [0, 0, 0, 0, 0], [0, 0, 0, 0, 0]]

Note that the inner range(5) function generates the number of columns and the outer range(4) generates the number of rows in the matrix. Also note that the index of M will vary from M[0][0] to M[4][5] If you want to initialize the matrix with some random numbers, you can do the following:

# ---ON IDLE---  
import random
M = [[random.randint(0,10) for x in range(4)] for y in range(3)]
print(M)

#. . . OUTPUT IS . . .
[[5, 9, 3, 4], [7, 6, 6, 0], [10, 1, 5, 5]]

8.5.1. Sample script (To initialize items in a matrix)
Write a program to take a N x M matrix and initialize all elements to 0 and then fill them up with input from the user.

In [11]:
# Write a program to take a N x M matrix and initialize all elements to 0
# and then fill them up with input from user.
R = int(input("Rows-> "))
C = int(input("Columns-> "))

M = [[0 for x in range(C)] for y in range(R)] # Initialize all to 0
for r in range(R):
    for c in range(C):
        M[r][c] = int(input('Input'))
print('Matrix is->', M)
Rows-> 2
Columns-> 3
Input1
Input2
Input3
Input4
Input5
Input6
Matrix is-> [[1, 2, 3], [4, 5, 6]]

8.5.2. Sample script (Get items in diagonals of a matrix)
Write a program to take an M x M square matrix of random integers and print the values of both the diagonals of the matrix.

In [12]:
# Write a program to take a M x M square matrix of random integers 
# and print the values of both the diagonals of the matrix
import random
R = int(input("Dimension of square matrix-> "))
C = R # For diagonals you must have square matrix
d1 = [] # For 1st diagonal
d2 = [] # For 2nd diagonal
M = [[random.randint(10,99) for x in range(C)] for y in range(R)] 
for i in range(R): # To print each row in a new line
    print(M[i])
for r in range(R):
    for c in range(C):
        if r == c: #For 1st diagonal
            d1.append(M[r][c])
        if r+c == R-1: # For 2nd diagonal
            d2.append(M[r][c])    
print('Diagonal d1 is-> ', d1)
print('Diagonal d2 is-> ', d2)
Dimension of square matrix-> 7
[61, 59, 75, 88, 94, 18, 89]
[86, 15, 94, 18, 32, 15, 99]
[76, 42, 97, 95, 76, 28, 85]
[14, 73, 47, 14, 40, 25, 40]
[45, 59, 76, 38, 45, 71, 60]
[69, 93, 19, 91, 88, 15, 62]
[70, 86, 97, 18, 24, 15, 22]
Diagonal d1 is->  [61, 15, 97, 14, 45, 15, 22]
Diagonal d2 is->  [89, 15, 76, 14, 76, 93, 70]

8.5.3. Sample script (Add all numbers whichare even in a matrix)
Write a program to generate an N x M matrix of random integers in range 0 to 20. Find the sum of all its even numbers.

In [13]:
# Write a program to generate a  N x M matrix of random integers
# in range 0 to 20. Find sum of all its even numbers. 
import random
R = int(input("Rows-> "))
C = int(input("Columns-> "))

M = [[random.randint(0, 20) for x in range(C)] for y in range(R)]
print(M)
s =0
for r in range(R):
    for c in range(C):
        if (M[r][c])%2 == 0:# For even numbers this is True
            print('Add', M[r][c])
            s = s + M[r][c]
print(s)
Rows-> 4
Columns-> 5
[[11, 20, 17, 5, 5], [8, 11, 16, 14, 9], [16, 13, 13, 1, 11], [13, 17, 10, 9, 4]]
Add 20
Add 8
Add 16
Add 14
Add 16
Add 10
Add 4
88

A script which takes a square matrix and prints its upper triangle matrix.

In [14]:
# A script which takes a square matrix and prints its upper triangle matrix. 
M = [[10,11,12,13],# This format makes the matrix more readable
     [14,15,16,17],
     [18,19,20,21],
     [22,23,24,25]]
print(M)
for r in range(4):
    for c in range(4):
        if r > c:
            M[r][c] = 0

print('Upper triangle matrix is->')
for i in range(4):
    print(M[i])
[[10, 11, 12, 13], [14, 15, 16, 17], [18, 19, 20, 21], [22, 23, 24, 25]]
Upper triangle matrix is->
[10, 11, 12, 13]
[0, 15, 16, 17]
[0, 0, 20, 21]
[0, 0, 0, 25]